// ==UserScript==
// @name         昱商达联-达人抓取
// @namespace    http://tampermonkey.net/
// @version      1.0.17
// @updateURL    https://gitee.com/fullband/yushangdalian-script-library/raw/master/daren.js
// @downloadURL  https://gitee.com/fullband/yushangdalian-script-library/raw/master/daren.js
// @description  抖音/快手/视频号：达人信息采集及上传到 ERP 系统
// @author       范腾飞
// @match        *://buyin.jinritemai.com/dashboard/servicehall/daren-profile*
// @match        *://cps.kwaixiaodian.com/zone/daren-match/daren-detail*
// @match        *://store.weixin.qq.com/shop/findersquare/finder-detail*
// @match        *://seller.lzsterp.com/*
// @icon         https://seller.lzsterp.com/favicon.ico
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_deleteValues
// @grant        GM_xmlhttpRequest
// @grant        GM_getTab
// @grant        GM_saveTab
// @grant        GM_getTabs
// ==/UserScript==

(function () {
    'use strict';
    // 全局常量
    const ERP_DOUYIN_DAREN_CLAIM_URL = 'https://epi.lzsterp.com/api/Daren/PlugDouyinTaked'; // 认领达人url
    const ERP_DOUYIN_DAREN_UPDATE_URL = 'https://epi.lzsterp.com/api/Daren/PlugDouyinUpdate'; // 更新达人url
    const ERP_DOUYIN_DAREN_SEA_URL = 'https://epi.lzsterp.com/api/Daren/PlugDouyinSea'; // 加入达人到公海url
    const ERP_DOUYIN_DAREN_CHECK_TAKED_URL = 'https://epi.lzsterp.com/api/Daren/DarenIsTaked'; // 检测达人是否已被认领urL

    const ERP_KWAI_DAREN_CLAIM_URL = 'https://epi.lzsterp.com/api/Daren/PlugKwaiTakedNew1'; // 认领达人url
    const ERP_KWAI_DAREN_UPDATE_URL = 'https://epi.lzsterp.com/api/Daren/PlugKwaiUpdateNew1'; // 更新达人url
    const ERP_KWAI_DAREN_SEA_URL = 'https://epi.lzsterp.com/api/Daren/PlugKwaiSeaNew1'; // 加入达人到公海url
    const ERP_KWAI_DAREN_CHECK_TAKED_URL = 'https://epi.lzsterp.com/api/Daren/DarenIsTaked'; // 检测达人是否已被认领urL

    const ERP_WEXIN_DAREN_CLAIM_URL = 'https://epi.lzsterp.com/api/Daren/PlugShiPinTaked'; // 认领达人url
    const ERP_WEXIN_DOUYIN_DAREN_UPDATE_URL = 'https://epi.lzsterp.com/api/Daren/PlugShiPinUpdate'; // 更新达人url
    const ERP_WEXIN_DOUYIN_DAREN_SEA_URL = 'https://epi.lzsterp.com/api/Daren/PlugShiPinToSea'; // 加入达人到公海url
    const ERP_WEXIN_DAREN_CHECK_TAKED_URL = 'https://seller.lzsterp.com/api/Daren/DarenIsTaked'; // 检测达人是否已被认领urL

    // ================== ERP 登录相关新增代码 开始 ==================
    // 替换原先 token 常量 & 函数
    const ERP_LOGIN_ORIGIN = 'https://seller.lzsterp.com';
    const ERP_LOGIN_URL = 'https://seller.lzsterp.com/#/login?redirect=/daren/myTalent';
    const ERP_LOGOUT_URL = 'https://epi.lzsterp.com/api/sysAuth/logout';

    const ERP_ACCESS_TOKEN_KEY = 'lzsterp_access_token';
    const ERP_REFRESH_TOKEN_KEY = 'lzsterp_refresh_token';
    const ERP_ACCOUNT_KEY = 'LZSTERP_ACCOUNT';   // 新增：账户名存储键

    let erpOverlayCreated = false;
    let erpPendingCallbacks = [];

    // ===== 抖音 uid 解析结束 =====

    // 聚合达人数据对象
    const darenInfo = {
        contact_info: { darenPhone: '', darenWechat: '', showPhone: '0', showWechat: '0' },
        profile_info: {
            darenId: '', dyUid: '', headUrl: '', agency: '', darenNick: '', darenLevel: '',
            mainCategory: '', creditScore: '', introduction: '',
            wineRatioNum: 0, liveShowCount: 0, videoPubCount: 0, score: ''
        },
        overview_info: {
            totalSale: '', darenType: '', totalSalesLow: 0, totalSalesHigh: 0
        },
        fans_info: {
            fansNum: 0, fansFeature: ''
        },
        goods_portrait_info: {
            brandRank: {}, categoryRank: {}
        },
        sales_analyses_info: {
            averagePrice: 0,
            livePercentage: 0, liveSaleLow: 0, liveSaleHigh: 0, liveAveragePrice: 0,
            videoPercentage: 0, videoSaleLow: 0, videoSaleHigh: 0, videoAveragePrice: 0
        }
    };

    function getErpAccessToken() {
        try {
            return typeof GM_getValue === 'function' ? GM_getValue(ERP_ACCESS_TOKEN_KEY, '') : '';
        } catch (_) { return ''; }
    }
    function getErpRefreshToken() {
        try {
            return typeof GM_getValue === 'function' ? GM_getValue(ERP_REFRESH_TOKEN_KEY, '') : '';
        } catch (_) { return ''; }
    }
    function setErpTokens(accessToken, refreshToken) {
        try {
            if (typeof GM_setValue === 'function') {
                if (accessToken) GM_setValue(ERP_ACCESS_TOKEN_KEY, accessToken);
                if (refreshToken) GM_setValue(ERP_REFRESH_TOKEN_KEY, refreshToken);
            }
        } catch (_) { }
    }

    function getErpAccount() {
        try { return typeof GM_getValue === 'function' ? (GM_getValue(ERP_ACCOUNT_KEY, '') || '') : ''; } catch (_) { return ''; }
    }
    function setErpAccount(acc) {
        try { if (typeof GM_setValue === 'function') GM_setValue(ERP_ACCOUNT_KEY, acc || ''); } catch (_) { }
    }
    function clearErpAuth() {
        try {

            if (typeof GM_setValue === 'function') {
                GM_setValue(ERP_ACCESS_TOKEN_KEY, '')
                GM_setValue(ERP_REFRESH_TOKEN_KEY, '')
                GM_setValue(ERP_ACCOUNT_KEY, '')
            }
            if (typeof GM_deleteValue === 'function') {
                GM_deleteValue(ERP_ACCESS_TOKEN_KEY)
                GM_deleteValue(ERP_REFRESH_TOKEN_KEY)
                GM_deleteValue(ERP_ACCOUNT_KEY)
            }

        } catch (_) { }
    }

    // 统一：是否已有登录
    function hasErpToken() {
        return !!getErpAccessToken();
    }

    function ensureErpToken(doneCb) {
        if (hasErpToken()) { doneCb && doneCb(); return; }
        if (doneCb) erpPendingCallbacks.push(doneCb);
        showErpLoginOverlay();
    }

    function fireErpReady() {
        if (!hasErpToken()) return;
        hideErpLoginOverlay();
        const list = erpPendingCallbacks.slice();
        erpPendingCallbacks = [];
        list.forEach(fn => { try { fn(); } catch (_) { } });
    }

    function showErpLoginOverlay() {
        if (erpOverlayCreated) return;
        erpOverlayCreated = true;

        const wrap = document.createElement('div');
        wrap.id = 'erp-login-overlay';
        Object.assign(wrap.style, {
            position: 'fixed', inset: '0', background: 'rgba(0,0,0,0.45)',
            zIndex: '999999', display: 'flex', alignItems: 'center', justifyContent: 'center'
        });

        const box = document.createElement('div');
        Object.assign(box.style, {
            width: '880px', height: '600px', background: '#fff', borderRadius: '8px',
            boxShadow: '0 4px 18px rgba(0,0,0,0.25)', position: 'relative', overflow: 'hidden'
        });

        const closeBtn = document.createElement('div');
        closeBtn.textContent = '×';
        Object.assign(closeBtn.style, {
            position: 'absolute', right: '12px', top: '6px', cursor: 'pointer',
            fontSize: '24px', lineHeight: '24px', color: '#999'
        });
        closeBtn.onclick = () => {
            // 手动关闭不继续动作
            hideErpLoginOverlay();
        };

        const tip = document.createElement('div');
        tip.textContent = '请在下方 ERP 登录后自动继续当前操作';
        Object.assign(tip.style, {
            padding: '10px 16px', fontSize: '14px', color: '#333', background: '#f5f7fa',
            borderBottom: '1px solid #eee'
        });

        const iframe = document.createElement('iframe');
        iframe.src = ERP_LOGIN_URL + '&_t=' + Date.now();
        Object.assign(iframe.style, { width: '100%', height: '100%', border: '0' });

        box.appendChild(closeBtn);
        box.appendChild(tip);
        box.appendChild(iframe);
        wrap.appendChild(box);
        document.body.appendChild(wrap);
    }

    function hideErpLoginOverlay() {
        const el = document.getElementById('erp-login-overlay');
        if (el) el.remove();
        erpOverlayCreated = false;
    }


    // 父页面收到登录 iframe 发来的成功消息后关闭
    window.addEventListener('message', e => {
        if (e.origin !== ERP_LOGIN_ORIGIN) return;
        if (e.data && e.data.type === 'lzsterp_token_set') {
            if (e.data.account) {
                setErpAccount(e.data.account);
            }
            console.log('[采集监控][ERP] 登录成功消息 account=', getErpAccount());
            fireErpReady();
            updateErpAccountDisplay();   // 新增：刷新底栏显示
        }
    });

    // iframe 页面内：拦截登录接口响应 (POST https://epi.lzsterp.com/api/sysAuth/login)
    (function hookLoginApiInIframe() {
        if (location.host !== 'seller.lzsterp.com') return;

        // 防重复
        if (window.__erp_login_hooked__) return;
        window.__erp_login_hooked__ = true;

        let loginAccountCandidate = ''; // 新增：暂存用户名

        function tryCaptureAccountFromBody(bodyStr) {
            if (!bodyStr) return;
            try {
                const obj = JSON.parse(bodyStr);
                if (obj && typeof obj === 'object') {
                    if (obj.account && typeof obj.account === 'string') {
                        loginAccountCandidate = obj.account;
                    }
                }
            } catch (_) { }
        }

        function tryHandleLoginResponse(url, text) {
            if (!/\/api\/sysAuth\/login(\?|$)/.test(url)) return;
            try {
                const data = JSON.parse(text || '{}');
                if (data && data.code === 200 && data.result) {
                    const accessToken = data.result.accessToken || '';
                    const refreshToken = data.result.refreshToken || '';
                    if (accessToken) {
                        setErpTokens(accessToken, refreshToken);
                        // 发送用户名（若请求体已捕获）
                        try {
                            window.top.postMessage({ type: 'lzsterp_token_set', account: loginAccountCandidate }, '*');
                        } catch (_) { }
                        console.log('[采集监控][ERP] 捕获登录 token 已保存 account=', loginAccountCandidate);
                    } else {
                        console.log('[采集监控][ERP] 登录响应中未找到 accessToken');
                    }
                } else {
                    console.log('[采集监控][ERP] 登录失败或格式不符 code=', data && data.code);
                }
            } catch (err) {
                console.log('[采集监控][ERP] 解析登录响应出错', err);
            }
        }

        // Hook fetch（增强：抓取 body 中 account）
        const rawFetch = window.fetch;
        window.fetch = function (input, init = {}) {
            try {
                const url = (typeof input === 'string') ? input : (input && input.url) || '';
                const method = (init && init.method) ? init.method.toUpperCase() : 'GET';
                if (/\/api\/sysAuth\/login(\?|$)/.test(url) && method === 'POST') {
                    const body = init.body;
                    if (typeof body === 'string') {
                        tryCaptureAccountFromBody(body);
                    }
                }
            } catch (_) { }
            const p = rawFetch.apply(this, arguments);
            p.then(resp => {
                const url = resp.url || (input && input.url) || '';
                if (/\/api\/sysAuth\/login(\?|$)/.test(url)) {
                    resp.clone().text().then(t => tryHandleLoginResponse(url, t));
                }
            }).catch(() => { });
            return p;
        };

        // Hook XHR（增强：抓取 send 的 body）
        const origOpen = XMLHttpRequest.prototype.open;
        const origSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function (method, url, ...rest) {
            this._login_url = url;
            this._login_method = (method || 'GET').toUpperCase();
            return origOpen.call(this, method, url, ...rest);
        };
        XMLHttpRequest.prototype.send = function (body) {
            try {
                if (this._login_url && /\/api\/sysAuth\/login(\?|$)/.test(this._login_url) && this._login_method === 'POST') {
                    if (typeof body === 'string') {
                        tryCaptureAccountFromBody(body);
                    }
                }
            } catch (_) { }
            this.addEventListener('readystatechange', () => {
                if (this.readyState === 4) {
                    const url = this.responseURL || this._login_url || '';
                    if (/\/api\/sysAuth\/login(\?|$)/.test(url)) {
                        tryHandleLoginResponse(url, this.responseText || '');
                    }
                }
            });
            return origSend.call(this, body);
        };

        console.log('[采集监控][ERP] 登录接口拦截已安装 (iframe)');
    })();

    // ================== ERP 登录相关新增代码 结束 ==================

    // ===== 新增：平台判定 =====
    const host = location.host;

    const platform = /buyin\.jinritemai\.com/.test(host) ? 'douyin'
        : /cps\.kwaixiaodian\.com/.test(host) ? 'kwai'
            : /store\.weixin\.qq\.com/.test(host) ? 'wechat'
                : 'unknown';

    // ===== 新增：抖音 uid 解析（类似快手 promoterId）=====
    if (platform === 'douyin') {
        let lastDyUid = '';

        function safeParseJSON(str) {
            try { return JSON.parse(str); } catch (_) { return null; }
        }
        function tryDecodeCandidate(raw) {
            if (!raw) return null;
            // 先尝试直接 JSON
            let obj = safeParseJSON(raw);
            if (obj) return obj;
            // URL 解码再试
            try {
                const dec = decodeURIComponent(raw);
                obj = safeParseJSON(dec);
                if (obj) return obj;
            } catch (_) { }
            // Base64 尝试
            try {
                // 处理可能带 % 编码的
                const b64 = raw.replace(/\s+/g, '');
                const txt = atob(b64);
                obj = safeParseJSON(txt);
                if (obj) return obj;
            } catch (_) { }
            return null;
        }

        function extractDouyinUid() {
            let uid = '';
            try {
                const sp = new URLSearchParams(location.search);

                // 1) 直接 uid=xxx
                uid = sp.get('uid') || '';

                // 2) params= (JSON / URL 编码 / Base64)
                if (!uid) {
                    const paramsRaw = sp.get('params');
                    if (paramsRaw) {
                        const parsed = tryDecodeCandidate(paramsRaw);
                        if (parsed && typeof parsed === 'object') {
                            uid = parsed.uid || parsed.dyUid || '';
                        }
                    }
                }

                // 3) hash 里可能也带（例如 #/xxx?uid=）
                if (!uid && location.hash.includes('uid=')) {
                    try {
                        const hashQuery = location.hash.split('?')[1] || '';
                        if (hashQuery) {
                            const hs = new URLSearchParams(hashQuery);
                            uid = hs.get('uid') || '';
                            if (!uid) {
                                const paramsRaw = hs.get('params');
                                if (paramsRaw) {
                                    const parsed = tryDecodeCandidate(paramsRaw);
                                    if (parsed) uid = parsed.uid || parsed.dyUid || '';
                                }
                            }
                        }
                    } catch (_) { }
                }

                if (uid && uid !== lastDyUid) {
                    lastDyUid = uid;
                    if (!darenInfo.profile_info.dyUid) {
                        darenInfo.profile_info.dyUid = uid;
                        console.log('[采集监控][douyin] 解析 URL 得到 dyUid =', uid);
                        // 如果希望在接口未返回前就先用它作为临时 darenId，可放开下一行：
                        // if (!darenInfo.profile_info.darenId) darenInfo.profile_info.darenId = uid;
                    }
                }
            } catch (_) { }
        }

        extractDouyinUid();
        // 监听路由 / hash / SPA 变化
        setInterval(extractDouyinUid, 800);
    }


    // ===== 接口匹配规则（抓取真实 response） =====
    const URL_PATTERNS = {
        douyin: [
            { key: 'profile_info', regex: /homePage\/author\/profile/ },
            { key: 'contact_info', regex: /api\/contact\/contact_info.*check_mode=2/ },
            { key: 'overview_info', regex: /homePage\/overview\/core/ },
            { key: 'fans_info', regex: /api\/authorStatData\/authorFansV2/ },
            { key: 'goods_portrait_info', regex: /homePage\/overview\/goods_portrait/ },
            { key: 'sales_analyses_info', regex: /api\/authorStatData\/salesAnalyseV2/ }
        ],
        wechat: [
            { key: 'overview_info', regex: /getSquareTalentList/ },
            { key: 'fans_info', regex: /getSquareFinderFanFeature/ }
        ],
        kwai: [
            { key: 'profile_info', regex: /promoter\/info\?[^?]*type=2/ },
            { key: 'contact_info', regex: /promoter\/contact\/info/ },
            { key: 'live_block', regex: /promoter\/live\/info/ },
            { key: 'video_block', regex: /promoter\/video\/analysis\/key\/indicator/ },
            { key: 'fans_info', regex: /promoter\/fans\/info/ },
            { key: 'fans_feature_block', regex: /promoter\/fans\/profile/ },
            { key: 'goods_portrait_info', regex: /promoter\/info\?[^?]*type=1/ },
            { key: 'sales_analyses_info', regex: /promoter\/cargo\/analysis\/key\/indicator/ }
        ]
    };

    // ===== 新增：接口完成标记集合，修复 endpointsLoaded 未定义错误 =====
    const endpointsLoaded = new Set();

    // ===== 新增：统一打印函数（修复 printDarenInfo 未定义） =====
    function printDarenInfo(tag) {
        try {
            console.log('[采集监控][汇总]', tag, JSON.parse(JSON.stringify(darenInfo)));
        } catch (e) {
            console.log('[采集监控][汇总]', tag, darenInfo);
        }
    }

    // 统一扁平化函数：取消原来的 if( typeof ... ) 包裹，直接定义避免 ReferenceError
    function flattenSubDicts(obj) {
        const out = {};
        if (!obj || typeof obj !== 'object') return out;
        Object.values(obj).forEach(v => {
            if (v && typeof v === 'object' && !Array.isArray(v)) {
                Object.entries(v).forEach(([k, val]) => { out[k] = val; });
            }
        });
        return out;
    }

    // ===== 新增：上传与认领检测依赖函数（修复 uploadToServer 未定义） 开始 =====
    function getPlatformCN(p) {
        if (p === 'douyin') return '抖音';
        if (p === 'kwai') return '快手';
        if (p === 'wechat') return '视频号';
        return '';
    }

    function showToast(msg, type = 'info', dur = 2500) {
        try {
            const colors = { info: '#409eff', success: '#52c41a', error: '#f56c6c', warn: '#e6a23c' };
            const el = document.createElement('div');
            el.textContent = msg;
            Object.assign(el.style, {
                position: 'fixed', top: '24px', left: '50%', transform: 'translateX(-50%)', padding: '10px 16px',
                background: colors[type] || colors.info, color: '#fff', borderRadius: '4px',
                fontSize: '13px', zIndex: 999999, boxShadow: '0 4px 12px rgba(0,0,0,.18)',
                maxWidth: '340px', lineHeight: '1.5', wordBreak: 'break-all'
            });
            document.body.appendChild(el);
            setTimeout(() => {
                el.style.transition = 'all .35s';
                el.style.opacity = '0';
                el.style.transform = 'translateY(-8px)';
                setTimeout(() => el.remove(), 380);
            }, dur);
        } catch (_) { }
    }

    function buildClaimCheckUrls(darenId) {
        const platCN = getPlatformCN(platform);
        if (!darenId || !platCN) return [];
        // 大小写两种兼容 (后端大小写不统一)
        return [
            `${ERP_DOUYIN_DAREN_CHECK_TAKED_URL}?DarenId=${encodeURIComponent(darenId)}&FromPlatform=${encodeURIComponent(platCN)}`,
            `${ERP_DOUYIN_DAREN_CHECK_TAKED_URL.replace('/api/Daren/', '/api/daren/')}?darenId=${encodeURIComponent(darenId)}&fromPlatform=${encodeURIComponent(platCN)}`
        ];
    }

    let __claimChecking = false;
    let __claimStatusCached = null;

    async function checkClaimStatus(retry = 0) {
        try {
            if (__claimChecking) return;
            const did = darenInfo.profile_info.darenId;
            if (!did) { if (retry < 8) setTimeout(() => checkClaimStatus(retry + 1), 600); return; }
            if (!hasErpToken()) { if (retry < 8) setTimeout(() => checkClaimStatus(retry + 1), 800); return; }
            __claimChecking = true;

            const urls = buildClaimCheckUrls(did);
            let claimed = false;

            async function reqByFetch(u) {
                try {
                    const r = await fetch(u, { headers: { 'Authorization': 'Bearer ' + getErpAccessToken() } });
                    if (r.status === 401 || r.status === 403) { handleAuthExpired(); return { stop: true }; }
                    if (!r.ok) return {};
                    const j = await r.json().catch(() => ({}));
                    return { j };
                } catch (_) { return {}; }
            }

            function reqByGM(u) {
                return new Promise(resolve => {
                    try {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: u,
                            headers: { 'Authorization': 'Bearer ' + getErpAccessToken() },
                            timeout: 10000,
                            onload: (r) => {
                                if (r.status === 401 || r.status === 403) { handleAuthExpired(); return resolve({ stop: true }); }
                                let j = {};

                                try { j = JSON.parse(r.responseText || '{}'); } catch (_) { }

                                if (j.code === 401 || j.code === 403) { handleAuthExpired(); return resolve({ stop: true }); }

                                resolve({ j });
                            },
                            onerror: () => resolve({}),
                            ontimeout: () => resolve({})
                        });
                    } catch (_) {
                        resolve({});
                    }
                });
            }

            const useGM = typeof GM_xmlhttpRequest === 'function';

            for (const u of urls) {
                let res = useGM ? await reqByGM(u) : await reqByFetch(u);
                if (res.stop) break;
                const j = res.j || {};
                if ((j.code === 200 || j.code === undefined) && j.result === true) {
                    claimed = true;
                    break;
                }
                if (j.code === 401 || j.code === 403) {
                    // 401 已在请求里处理
                    // 用户登录失效，清空保存的登录信息，并要求用户重新登录
                    handleAuthExpired();
                }
            }

            __claimStatusCached = claimed;
            toggleClaimButton(!claimed);
        } finally {
            __claimChecking = false;
        }
    }

    function toggleClaimButton(show) {
        const btn = document.getElementById('claim-daren');
        if (btn) btn.style.display = show ? '' : 'none';
    }

    function handleAuthExpired() {
        showToast('认证失效，请重新登录', 'error');
        clearErpAuth();
        updateErpAccountDisplay();
        ensureErpToken(() => checkClaimStatus());
    }

    function getUploadEndpoint(action) {
        if (platform === 'douyin') {
            if (action === 'claim') return ERP_DOUYIN_DAREN_CLAIM_URL;
            if (action === 'update') return ERP_DOUYIN_DAREN_UPDATE_URL;
            if (action === 'sea') return ERP_DOUYIN_DAREN_SEA_URL;
        } else if (platform === 'kwai') {
            if (action === 'claim') return ERP_KWAI_DAREN_CLAIM_URL;
            if (action === 'update') return ERP_KWAI_DAREN_UPDATE_URL;
            if (action === 'sea') return ERP_KWAI_DAREN_SEA_URL;
        } else if (platform === 'wechat') {
            if (action === 'claim') return ERP_WEXIN_DAREN_CLAIM_URL;
            if (action === 'update') return ERP_WEXIN_DOUYIN_DAREN_UPDATE_URL;
            if (action === 'sea') return ERP_WEXIN_DOUYIN_DAREN_SEA_URL;
        }
        return '';
    }

    async function uploadToServer(dInfo, action) {
        const url = getUploadEndpoint(action);
        if (!url) {
            showToast('上传接口缺失', 'error');
            return { ok: false, msg: '接口缺失' };
        }
        if (!hasErpToken()) {
            handleAuthExpired();
            return { ok: false, msg: '未登录' };
        }
        const payload = flattenSubDicts(dInfo);

        // ===== 新增：上传前字段规范化 =====
        // darenLevel 强制为字符串
        if ('darenLevel' in payload) {
            try { payload.darenLevel = String(payload.darenLevel ?? ''); } catch (_) { payload.darenLevel = ''; }
        }

        if ('darenId' in payload) {
            try { payload.darenId = String(payload.darenId ?? ''); } catch (_) { payload.darenId = ''; }
        }

        function normalizeRankField(val) {
            if (val == null) return '';
            // 已经是符合格式的字符串
            if (typeof val === 'string') {
                const trimmed = val.trim();
                if (trimmed.startsWith('[') && trimmed.endsWith(']')) return trimmed;
                // 尝试把可能的单引号 JSON 转换
                try {
                    const repaired = trimmed.replace(/'/g, '"');
                    const parsed = JSON.parse(repaired);
                    if (Array.isArray(parsed)) return JSON.stringify(parsed);
                } catch (_) { }
                // 其它字符串不处理，按空处理
                return '';
            }
            // 数组：直接序列化
            if (Array.isArray(val)) {
                return JSON.stringify(val.map(it => {
                    if (it && typeof it === 'object') {
                        return {
                            name: it.name ?? '',
                            ratio: typeof it.ratio === 'number' ? it.ratio : (typeof it.rate === 'number' ? it.rate : 0)
                        };
                    }
                    return { name: String(it), ratio: 0 };
                }));
            }
            // 对象：转 entries
            if (typeof val === 'object') {
                const arr = Object.entries(val).map(([k, v]) => {
                    if (v && typeof v === 'object') {
                        const ratio = typeof v.ratio === 'number' ? v.ratio
                            : typeof v.rate === 'number' ? v.rate
                                : 0;
                        return { name: k, ratio };
                    }
                    return { name: k, ratio: (typeof v === 'number' ? v : 0) };
                });
                return JSON.stringify(arr);
            }
            return '';
        }

        // 处理 brandRank / categoryRank
        try {
            if ('brandRank' in payload) payload.brandRank = normalizeRankField(payload.brandRank);
            if ('categoryRank' in payload) payload.categoryRank = normalizeRankField(payload.categoryRank);
        } catch (_) { }

        // ===== 新增：上传前字段规范化结束 =====

        // 内部：统一发送（GM 优先，失败可降级 fetch）
        function gmPost(u) {
            console.log(getErpAccessToken());
            console.log(JSON.stringify(payload));
            return new Promise(resolve => {
                try {
                    var gm = GM_xmlhttpRequest({
                        method: 'POST',
                        url: u,
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                            'authorization': 'Bearer ' + getErpAccessToken()
                        },
                        data: JSON.stringify(payload),
                        timeout: 15000,
                        onload: (r) => resolve({ type: 'gm', url: u, resp: r }),
                        onerror: (r) => resolve({ type: 'gm', url: u, error: true, resp: r }),
                        ontimeout: (r) => resolve({ type: 'gm', url: u, timeout: true, resp: r })
                    });

                    console.log(gm);
                } catch (e) {
                    resolve({ type: 'gm', url: u, thrown: e });
                }
            });
        }
        async function fetchPost(u) {
            try {
                const res = await fetch(u, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json',
                        'Authorization': 'Bearer ' + getErpAccessToken()
                    },
                    body: JSON.stringify(payload),
                    credentials: 'include'
                });
                const text = await res.text();
                return { type: 'fetch', url: u, status: res.status, headers: Object.fromEntries(res.headers.entries()), text };
            } catch (e) {
                return { type: 'fetch', url: u, thrown: e };
            }
        }

        async function parseAndHandle(result, originalUrl, triedLower) {
            // 标准化字段
            let status, headersStr = '', bodyText = '';
            if (result.type === 'gm') {
                const r = result.resp || {};
                status = r.status;
                headersStr = r.responseHeaders || '';
                bodyText = r.response || r.responseText || '';
            } else {
                status = result.status;
                headersStr = JSON.stringify(result.headers || {});
                bodyText = result.text || '';
            }

            // 处理 JSON
            let jsonObj = null;
            if (bodyText && bodyText.trim().startsWith('{')) {
                try { jsonObj = JSON.parse(bodyText); } catch (_) { }
            }

            // 401
            if (jsonObj.code === 401 || jsonObj.code === 403) {
                handleAuthExpired();
                return { ok: false, msg: '401' };
            }


            // 命中业务成功
            if (jsonObj && jsonObj.code === 200) {
                showToast(jsonObj.result || '上传成功', 'success');
                if (action === 'claim') setTimeout(() => checkClaimStatus(), 400);
                return { ok: true, msg: jsonObj.result || 'success' };
            }
            if (jsonObj && (jsonObj.code === 401 || jsonObj.code === 403)) {
                handleAuthExpired();
                return { ok: false, msg: '401' };
            }

            // 判断是否 OSS MethodNotAllowed(大小写或路由未命中)
            const isXml = /<\?xml/.test(bodyText) && /MethodNotAllowed/.test(bodyText);
            const is405 = status === 405;
            const looksLikeOss = /oss-cn-|<HostId>/.test(bodyText) || /application\/xml/i.test(headersStr);
            if (!triedLower && (is405 || isXml || looksLikeOss)) {
                const lowerUrl = originalUrl.replace('/api/Daren/', '/api/daren/'); // 大小写降级
                if (lowerUrl !== originalUrl) {
                    console.log(result);
                    console.log('[采集监控][重试] 触发大小写降级: ', originalUrl, ' -> ', lowerUrl);
                    return await doRequest(lowerUrl, true);
                }
            }

            // 其它失败
            if (jsonObj) {
                showToast(jsonObj.message || '上传失败', 'error');
                return { ok: false, msg: jsonObj.message || 'fail' };
            } else {
                showToast(`上传失败(${status || '网络'})`, 'error');
                return { ok: false, msg: `status:${status}` };
            }
        }

        async function doRequest(u, triedLower = false) {
            // 优先 GM
            if (typeof GM_xmlhttpRequest === 'function') {
                const gmRes = await gmPost(u);
                return await parseAndHandle(gmRes, u, triedLower);
            } else {
                const fRes = await fetchPost(u);
                return await parseAndHandle(fRes, u, triedLower);
            }
        }

        try {
            return await doRequest(url, false);
        } catch (e) {
            showToast('上传异常', 'error');
            return { ok: false, msg: '异常' };
        }
    }
    // ===== 新增：上传与认领检测依赖函数 结束 =====

    // ===== 修改 REQUIRED_KEYS 按平台动态 =====
    let REQUIRED_KEYS = ['profile_info', 'overview_info', 'fans_info', 'goods_portrait_info', 'sales_analyses_info'];
    if (platform === 'wechat') REQUIRED_KEYS = ['overview_info', 'fans_info'];


    // ===== 截取请求 =====
    // Hook XHR 
    // Hook Fetch
    let xhrHooked = false;
    function hookXHRWhenReady() {
        if (xhrHooked) return;
        xhrHooked = true;
        const origOpen = XMLHttpRequest.prototype.open;
        const origSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function (method, url, ...rest) {
            this._mon_url = url;
            return origOpen.call(this, method, url, ...rest);
        };
        XMLHttpRequest.prototype.send = function (body) {
            this.addEventListener('readystatechange', () => {
                if (this.readyState === 4) {
                    try {
                        const url = this.responseURL || this._mon_url;
                        if (!started) return;
                        const matched = match(url);
                        if (matched.length === 0) return;
                        let json; try { json = JSON.parse(this.responseText || '{}'); } catch (_) { }
                        processResponse(url, json);
                    } catch (_) { }
                }
            });
            return origSend.call(this, body);
        };
        console.log('[采集监控] XHR劫持已启用');
    };

    (function hookFetch() {
        if (window.__daren_fetch_hooked__) return;
        window.__daren_fetch_hooked__ = true;
        const rawFetch = window.fetch;
        window.fetch = function (...args) {
            const p = rawFetch.apply(this, args);
            p.then(resp => {
                if (!started) return;
                const url = resp.url;
                if (!findKey(url)) return;
                resp.clone().text().then(txt => {
                    let j; try { j = JSON.parse(txt); } catch (_) { }
                    processResponse(url, j);
                }).catch(() => { });
            }).catch(() => { });
            return p;
        };
        console.log('[采集监控] fetch hook installed');
    })();

    // ===== 截取请求 =====


    // ===== 微信小店：立即启动（抖音仍依赖标题 title="达人主页" 如果条件存在立即注入） =====
    let started = false;
    if (platform != 'douyin') {
        started = true;
        hookXHRWhenReady();
        console.log('[采集监控] 非抖音平台立即 started');
    }

    // ===== 抖音标题触发监听（新增） 抖音达人页面会跳转几次 最后一个页面才是要监听的页面=====
    if (platform === 'douyin') {
        const DOUYIN_TARGET_TITLE = '达人主页';
        function startDouyin() {
            if (started) return;

            // 轮询标题，命中后开始输出并劫持 XHR
            const titleTimer = setInterval(() => {
                if (started) {
                    clearInterval(titleTimer);
                    return;
                }

                if (document.title.trim() === DOUYIN_TARGET_TITLE) {
                    started = true;
                    console.log('[采集监控] 已开始监听 (title=达人主页)');
                    hookXHRWhenReady();
                    clearInterval(titleTimer);
                }
            }, 100);

            return false;
        }
        // 页面初始已是目标标题
        if (!startDouyin()) {
            const titlePollTimer = setInterval(() => {
                if (startDouyin()) clearInterval(titlePollTimer);
            }, 100);
        }
    }

    // ===== 快手 promoterId 解析与轮询 =====
    let kwaiPromoterId = '';
    if (platform === 'kwai') {
        const parsePromoterId = () => {
            if (kwaiPromoterId) return;
            try {
                const u = new URL(location.href);
                kwaiPromoterId = u.searchParams.get('promoterId') || '';
                if (kwaiPromoterId) {
                    // ===== 修复：快手提前写入 darenId，避免后续认领状态判断因 "" 失效 =====
                    if (!darenInfo.profile_info.darenId) {
                        darenInfo.profile_info.darenId = kwaiPromoterId;
                        // 若已经登录则尽快检查一次认领状态
                        if (hasErpToken()) {
                            try { checkClaimStatus(); } catch (_) { }
                        }
                    }
                    console.log('[采集监控][kwai] 解析到 promoterId =', kwaiPromoterId);
                    createBottomBarIfNeeded();
                }
            } catch (_) { }
        };
        parsePromoterId();
        const kwaiParseTimer = setInterval(() => {
            if (kwaiPromoterId) { clearInterval(kwaiParseTimer); return; }
            parsePromoterId();
        }, 100);
    }

    // ===== 快手主动构造请求（参考 kwai.py） =====
    let kwaiFetching = false;
    async function fetchKwaiData(force = false) {
        if (platform !== 'kwai') return;
        if (!kwaiPromoterId) {
            try {
                const u = new URL(location.href);
                kwaiPromoterId = u.searchParams.get('promoterId') || '';
            } catch (_) { }
        }
        if (!kwaiPromoterId) {
            console.log('[采集监控][kwai] 无 promoterId，无法抓取');
            return;
        }
        if (kwaiFetching && !force) return;
        kwaiFetching = true;
        console.log('[采集监控][kwai] 开始构造请求抓取数据 promoterId=', kwaiPromoterId);

        const cookieHeader = document.cookie || '';

        async function g(url, opt = {}) {
            try {
                const r = await fetch(url, {
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json',
                        'Cookie': cookieHeader
                    },
                    ...opt
                });
                if (!r.ok) return null;
                return await r.json();
            } catch (e) {
                return null;
            }
        }

        try {
            // 1 基础信息 type=2
            const info2 = await g(`https://cps.kwaixiaodian.com/distribute/pc/seller/promoter/info?promoterId=${kwaiPromoterId}&type=2`);
            if (info2?.data) {
                const d = info2.data;
                darenInfo.profile_info.darenId = kwaiPromoterId;
                darenInfo.profile_info.headUrl = d.promoterHeadImgUrl || '';
                darenInfo.profile_info.darenNick = d.promoterNickName || '';
                darenInfo.profile_info.darenLevel = '0';
                if (Array.isArray(d.hotSaleChannelInfo)) {
                    darenInfo.profile_info.mainCategory = d.hotSaleChannelInfo.map(x => x.hotSaleChannelName || '').filter(Boolean).join(',');
                }
                endpointsLoaded.add('profile_info');
                printDarenInfo('kwai_profile_active');
                if (darenInfo.profile_info.darenId) createBottomBarIfNeeded();
            }

            // 2 联系方式
            const contact = await g(`https://cps.kwaixiaodian.com/distribute/pc/seller/promoter/contact/info?promoterId=${kwaiPromoterId}&type=2`);
            if (contact?.data) {
                const cd = contact.data;
                try {
                    if (cd.phone) {
                        let phone = atob(cd.phone);
                        phone = phone.replace(/^\+86/, '').trim();
                        darenInfo.contact_info.darenPhone = phone;
                        if (phone) darenInfo.contact_info.showPhone = '1';
                    }
                    if (cd.wechat) {
                        let wechat = atob(cd.wechat);
                        darenInfo.contact_info.darenWechat = wechat;
                        if (wechat) darenInfo.contact_info.showWechat = '1';
                    }
                } catch (_) { }
                endpointsLoaded.add('contact_info');
                printDarenInfo('kwai_contact_active');
            }

            // 3 直播信息
            const live = await g(`https://cps.kwaixiaodian.com/distribute/pc/seller/promoter/live/info?timeRangeType=1&promoterId=${kwaiPromoterId}&type=2`);
            if (live?.data) {
                const v = live.data.staticDataInfoView || {};
                darenInfo.profile_info.liveShowCount = v.promoteLiveCount || 0;
                // kwai.py 中 liveVisitorCount 属于 sales_analyses_info
                darenInfo.sales_analyses_info.liveVisitorCount = v.liveStreamVisitorCount || 0;
            }

            // 4 视频信息
            const video = await g(`https://cps.kwaixiaodian.com/gateway/distribute/platform/seller/promoter/video/analysis/key/indicator?promoterId=${kwaiPromoterId}&timeRangeType=2`);
            if (video?.data) {
                const vd = video.data;
                darenInfo.profile_info.videoPubCount = vd.videoCount || 0;
                // 视频销售额范围 videoSaleLow / High
                const rawSale = vd.totalSale || '';
                const cleaned = rawSale.replace(/元|万/g, '').trim();
                if (cleaned.includes('-')) {
                    const [a, b] = cleaned.split('-');
                    const low = parseFloat(a) || 0;
                    const high = parseFloat(b) || low;
                    if (/万/.test(rawSale)) {
                        darenInfo.sales_analyses_info.videoSaleLow = Math.ceil(low * 10000);
                        darenInfo.sales_analyses_info.videoSaleHigh = Math.ceil(high * 10000);
                    } else {
                        darenInfo.sales_analyses_info.videoSaleLow = Math.ceil(low);
                        darenInfo.sales_analyses_info.videoSaleHigh = Math.ceil(high);
                    }
                }
                // 视频播放量 videoWatchCount
                if (typeof vd.videoWatchCount === 'number')
                    darenInfo.sales_analyses_info.videoWatchCount = vd.videoWatchCount;
            }

            // 5 粉丝特征 (POST)
            const fansProfile = await g(`https://cps.kwaixiaodian.com/gateway/distribute/platform/seller/promoter/fans/profile`, {
                method: 'POST',
                body: JSON.stringify({ promoterId: kwaiPromoterId })
            });
            if (fansProfile?.data) {
                const arr = fansProfile.data.maxResult || [];
                darenInfo.fans_info.fansFeature = arr
                    .map(f => `${f.label || ''}居多(${f.rate || 0}%)`)
                    .filter(Boolean).join(',');
            }

            // 6 粉丝趋势
            const fansInfo = await g(`https://cps.kwaixiaodian.com/distribute/pc/seller/promoter/fans/info?promoterId=${kwaiPromoterId}&timeRangeType=2`);
            if (fansInfo?.data) {
                const trend = fansInfo.data.fansNumTrend || [];
                if (trend.length) {
                    darenInfo.fans_info.fansNum = trend[trend.length - 1].fansTotalAmount || 0;
                }
                endpointsLoaded.add('fans_info');
            }

            // 7 品类/品牌 (type=1)
            const catBrand = await g(`https://cps.kwaixiaodian.com/distribute/pc/seller/promoter/info?promoterId=${kwaiPromoterId}&type=1`);
            if (catBrand?.data) {
                const cat = catBrand.data.hotSaleChannelInfo || [];
                const brand = catBrand.data.hotSaleBrandInfo || [];
                darenInfo.goods_portrait_info.categoryRank = cat.map(c => ({
                    name: c.hotSaleChannelName,
                    ratio: (c.hotSaleChannelRate || 0) / 10000
                }));
                const totalWeight = brand.length ? brand.length * (brand.length + 1) / 2 : 0;
                darenInfo.goods_portrait_info.brandRank = brand.map(b => {
                    const r = b.hotSaleBrandNo;
                    return {
                        name: b.hotSaleBrandTitle,
                        ratio: totalWeight ? ((brand.length - r + 1) / totalWeight) : 0
                    };
                });
                endpointsLoaded.add('goods_portrait_info');
            }

            // 8 带货分析
            const sales = await g(`https://cps.kwaixiaodian.com/gateway/distribute/platform/seller/promoter/cargo/analysis/key/indicator?promoterId=${kwaiPromoterId}&timeRangeType=2`);
            if (sales?.data) {
                const d = sales.data;
                darenInfo.sales_analyses_info.averagePrice = (d.avgItemPrice || 0) / 100;
                let totalSaleStr = d.totalSale || '';
                if (/万/.test(totalSaleStr)) {
                    const cleaned = totalSaleStr.replace(/万|元/g, '').trim();
                    if (cleaned.includes('-')) {
                        const [a, b] = cleaned.split('-');
                        darenInfo.overview_info.totalSale =
                            `${Math.round(parseFloat(a || 0) * 10000)}-${Math.round(parseFloat(b || a || 0) * 10000)}`;
                    } else {
                        darenInfo.overview_info.totalSale = '' + Math.round(parseFloat(cleaned || 0) * 10000);
                    }
                } else {
                    darenInfo.overview_info.totalSale = totalSaleStr;
                }
                if (darenInfo.overview_info.totalSale.includes('-')) {
                    const [l, h] = darenInfo.overview_info.totalSale.split('-');
                    darenInfo.overview_info.totalSalesLow = parseInt(l) || 0;
                    darenInfo.overview_info.totalSalesHigh = parseInt(h) || 0;
                }
                endpointsLoaded.add('sales_analyses_info');
                endpointsLoaded.add('overview_info');
            }

            printDarenInfo('kwai_active_full');

        } catch (err) {
            console.log('[采集监控][kwai] 主动抓取异常', err);
        } finally {
            kwaiFetching = false;
        }
    }

    // ===== 修复：确保 processResponse 在被引用前已存在，避免 ReferenceError =====
    if (typeof processResponse !== 'function') {

        var processResponse = () => { };
        // 占位: 默认不做任何事
        // function processResponse(/* url, json */) {}
    }

    // ===== 覆盖 / 扩展 processResponse：增加 wechat 解析 =====
    const _origProcessResponse = processResponse;

    function findKey(url) {
        const list = URL_PATTERNS[platform] || [];
        for (const p of list) {
            if (p.regex.test(url)) return p.key;
        }
        return null;
    }
    function match(url) { return findKey(url) ? [findKey(url)] : []; }

    // ===== 重写 processResponse：解析各平台响应 =====
    var processResponse = function (url, json) {
        if (!json) return;
        const k = findKey(url);
        if (!k) return;

        // ---- 抖音 ----
        if (platform === 'douyin') {
            if (k === 'profile_info') {
                const data = json.data || {};
                darenInfo.profile_info.darenId = data.account_douyin || darenInfo.profile_info.darenId;
                darenInfo.profile_info.headUrl = data.avatar || '';
                darenInfo.profile_info.darenNick = data.nickname || '';
                darenInfo.profile_info.darenLevel = data.level || '';
                darenInfo.profile_info.agency = data.agency || '';
                darenInfo.profile_info.introduction = data.introduction || '';
                darenInfo.profile_info.score = (data.score ?? '') + '';
                darenInfo.profile_info.creditScore = String(Math.ceil((data.credit_score || 0) / 100));
                const pm = data.product_main_type || {};
                if (Object.keys(pm).length) {
                    darenInfo.profile_info.mainCategory = Object.keys(pm).join(',');
                    for (const name in pm) {
                        if (name.includes('酒')) {
                            const v = parseFloat(pm[name]);
                            if (!isNaN(v)) darenInfo.profile_info.wineRatioNum = v;
                            break;
                        }
                    }
                }
                endpointsLoaded.add('profile_info');
                printDarenInfo('douyin_profile');
                if (darenInfo.profile_info.darenId) createBottomBarIfNeeded();
            } else if (k === 'contact_info') {
                const v = (json.data?.contact_info?.contact_value) || '';
                if (v.includes('(+')) {
                    darenInfo.contact_info.darenPhone = v.split('(')[0].trim();
                    if (darenInfo.contact_info.darenPhone) darenInfo.contact_info.showPhone = '1';
                } else {
                    darenInfo.contact_info.darenWechat = v;
                    if (darenInfo.contact_info.darenWechat) darenInfo.contact_info.showWechat = '1';
                }
                endpointsLoaded.add('contact_info');
                printDarenInfo('douyin_contact');
            } else if (k === 'overview_info') {
                const data = json.data || {};
                const details = data.content_data?.details || [];
                let darenType = '';
                let liveCount = 0;
                let videoCount = 0;
                details.forEach(it => {
                    if ('tag' in it && !darenType) darenType = it.name || '';
                    if (it.name === '直播') liveCount = it.count || 0;
                    if (it.name === '视频') videoCount = it.count || 0;
                });
                if (darenType) darenType += '达人';
                darenInfo.profile_info.liveShowCount = liveCount;
                darenInfo.profile_info.videoPubCount = videoCount;
                const sell = data.sell_data || {};
                darenInfo.overview_info.totalSalesLow = sell.total_sales_low || 0;
                darenInfo.overview_info.totalSalesHigh = sell.total_sales_high || 0;
                const low = sell.total_sales_low ?? '';
                const high = sell.total_sales_high ?? '';
                darenInfo.overview_info.totalSale = `${low}${(low !== '' && high !== '') ? '-' : ''}${high}`;
                darenInfo.overview_info.darenType = darenType;
                endpointsLoaded.add('overview_info');
                printDarenInfo('douyin_overview');
            } else if (k === 'fans_info') {
                const data = json.data || {};
                const arr = data.trend_total?.y || [];
                if (arr.length) darenInfo.fans_info.fansNum = arr[arr.length - 1];
                const analysis = data.analysis || [];
                for (const a of analysis) {
                    if (a.title === '粉丝特征' && Array.isArray(a.value)) {
                        darenInfo.fans_info.fansFeature = a.value.join(',');
                        break;
                    }
                }
                endpointsLoaded.add('fans_info');
                printDarenInfo('douyin_fans');
            } else if (k === 'goods_portrait_info') {
                const data = json.data || {};
                darenInfo.goods_portrait_info.brandRank = data.brand_top?.details || {};
                darenInfo.goods_portrait_info.categoryRank = data.category_top?.details || {};
                endpointsLoaded.add('goods_portrait_info');
                printDarenInfo('douyin_goods_portrait');
            } else if (k === 'sales_analyses_info') {
                const data = json.data || {};
                const kd = data.key_data || {};
                const ld = data.live_data || {};
                const vd = data.video_data || {};
                darenInfo.sales_analyses_info.averagePrice = (kd.average_price || 0) / 100;
                darenInfo.sales_analyses_info.livePercentage = (ld.percentage || 0) / 100;
                darenInfo.sales_analyses_info.liveSaleLow = ld.sale_low || 0;
                darenInfo.sales_analyses_info.liveSaleHigh = ld.sale_high || 0;
                darenInfo.sales_analyses_info.liveAveragePrice = (ld.average_price || 0) / 100;
                darenInfo.sales_analyses_info.videoPercentage = (vd.percentage || 0) / 100;
                darenInfo.sales_analyses_info.videoSaleLow = vd.sale_low || 0;
                darenInfo.sales_analyses_info.videoSaleHigh = vd.sale_high || 0;
                darenInfo.sales_analyses_info.videoAveragePrice = (vd.average_price || 0) / 100;
                endpointsLoaded.add('sales_analyses_info');
                printDarenInfo('douyin_sales');
            }
        }

        // ---- 视频号（微信小店） ----
        if (platform === 'wechat') {
            if (k === 'overview_info') {
                const list = json.list || [];
                if (list.length) {
                    const t = list[0];
                    darenInfo.profile_info.darenId = t.talentAppid || t.talentAppId || darenInfo.profile_info.darenId;
                    darenInfo.profile_info.headUrl = t.finderInfo?.headImg || '';
                    darenInfo.profile_info.darenNick = (t.accountList || []).find(a => a.accountType === 1)?.nickname || '';
                    darenInfo.profile_info.introduction = t.finderInfo?.introduction || '';
                    darenInfo.fans_info.fansNum = t.finderInfo?.fansNumber || 0;
                    // GMV
                    const gmv = t.promotionInfo?.gmv || '';
                    const nums = (gmv.match(/(\d+\.?\d*)/g) || []).map(n => parseFloat(n));
                    let mul = /万/.test(gmv) ? 10000 : /千/.test(gmv) ? 1000 : /百/.test(gmv) ? 100 : 1;
                    if (!nums.length) {
                        darenInfo.overview_info.totalSale = '0-0';
                    } else if (nums.length === 1) {
                        const v = Math.round(nums[0] * mul);
                        darenInfo.overview_info.totalSale = `0-${v}`;
                        darenInfo.overview_info.totalSalesHigh = v;
                    } else {
                        const lo = Math.round(Math.min(...nums) * mul);
                        const hi = Math.round(Math.max(...nums) * mul);
                        darenInfo.overview_info.totalSale = `${lo}-${hi}`;
                        darenInfo.overview_info.totalSalesLow = lo;
                        darenInfo.overview_info.totalSalesHigh = hi;
                    }
                    // 分类 / 酒类占比 / mainCategory
                    const cats = t.finderInfo?.topCatList || [];
                    const names = cats.map(c => c.topCatName).filter(Boolean);
                    darenInfo.profile_info.mainCategory = names.join(',');
                    const wineObj = cats.find(c => c.topCatName === '酒类');
                    if (wineObj) darenInfo.profile_info.wineRatioNum = (wineObj.topCatPercentage || 0) / 10000;
                    darenInfo.goods_portrait_info.categoryRank = cats.map(c => ({
                        name: c.topCatName,
                        ratio: (c.topCatPercentage || 0) / 1000000
                    }));
                    darenInfo.profile_info.liveShowCount = parseInt(t.promotionInfo?.liveCnt || 0);
                    darenInfo.profile_info.videoPubCount = parseInt(t.promotionInfo?.videoCnt || 0);
                    darenInfo.overview_info.darenType = (t.finderInfo?.contactList || []).map(c => c.name).filter(Boolean)[0] || '';
                    endpointsLoaded.add('profile_info');
                    endpointsLoaded.add('overview_info');
                    endpointsLoaded.add('goods_portrait_info'); // 用 categoryRank 代表
                    printDarenInfo('wechat_profile_overview');
                    if (darenInfo.profile_info.darenId) createBottomBarIfNeeded();
                }
            } else if (k === 'fans_info') {
                const ffi = json.fanFeatureInfo || {};
                const pairs = [
                    ['featureSex', 'featureSexRatio'],
                    ['featureAge', 'featureAgeRatio'],
                    ['featureProvince', 'featureProvinceRatio'],
                    ['featureEcomUserLevel', 'featureEcomUserLevelRatio'],
                    ['perUvPrice', 'perUvPriceRatio']
                ];
                const arr = [];
                pairs.forEach(([dKey, rKey]) => {
                    const desc = ffi[dKey];
                    const ratio = ffi[rKey];
                    if (desc && typeof ratio === 'number') {
                        arr.push(`${desc}(${(ratio / 10000).toFixed(2)}%)`);
                    }
                });
                if (arr.length) {
                    darenInfo.fans_info.fansFeature = arr.join(',');
                    endpointsLoaded.add('fans_info');
                    printDarenInfo('wechat_fans');
                }
            }
        }

        // ---- 快手 ----
        if (platform === 'kwai') {
            if (k === 'profile_info') {
                const d = json.data || {};
                darenInfo.profile_info.darenId = d.promoterId || darenInfo.profile_info.darenId;
                darenInfo.profile_info.headUrl = d.promoterHeadImgUrl || '';
                darenInfo.profile_info.darenNick = d.promoterNickName || '';
                darenInfo.profile_info.darenLevel = '0';
                if (Array.isArray(d.hotSaleChannelInfo)) {
                    darenInfo.profile_info.mainCategory = d.hotSaleChannelInfo.map(x => x.hotSaleChannelName || '').filter(Boolean).join(',');
                }
                endpointsLoaded.add('profile_info');
                printDarenInfo('kwai_profile_hook');
                if (darenInfo.profile_info.darenId) createBottomBarIfNeeded();
            } else if (k === 'contact_info') {
                const cd = json.data || {};
                try {
                    if (cd.phone) {
                        let p = atob(cd.phone).replace(/^\+86/, '').trim();
                        darenInfo.contact_info.darenPhone = p;
                        if (p) darenInfo.contact_info.showPhone = '1';
                    }
                    if (cd.wechat) {
                        let w = atob(cd.wechat);
                        darenInfo.contact_info.darenWechat = w;
                        if (w) darenInfo.contact_info.showWechat = '1';
                    }
                } catch (_) { }
                endpointsLoaded.add('contact_info');
                printDarenInfo('kwai_contact_hook');
            } else if (k === 'live_block') {
                const v = json.data?.staticDataInfoView || {};
                darenInfo.profile_info.liveShowCount = v.promoteLiveCount || 0;
                darenInfo.sales_analyses_info.liveVisitorCount = v.liveStreamVisitorCount || 0;
            } else if (k === 'video_block') {
                const vd = json.data || {};
                darenInfo.profile_info.videoPubCount = vd.videoCount || 0;
                const ts = (vd.totalSale || '').replace(/元|万/g, '').trim();
                if (ts.includes('-')) {
                    const [a, b] = ts.split('-');
                    const low = parseFloat(a) || 0;
                    const high = parseFloat(b) || low;
                    if (/万/.test(vd.totalSale || '')) {
                        darenInfo.sales_analyses_info.videoSaleLow = Math.ceil(low * 10000);
                        darenInfo.sales_analyses_info.videoSaleHigh = Math.ceil(high * 10000);
                    } else {
                        darenInfo.sales_analyses_info.videoSaleLow = Math.ceil(low);
                        darenInfo.sales_analyses_info.videoSaleHigh = Math.ceil(high);
                    }
                }
            } else if (k === 'fans_feature_block') {
                const arr = json.data?.maxResult || [];
                darenInfo.fans_info.fansFeature = arr.map(f => `${f.label || ''}居多(${f.rate || 0}%)`).filter(Boolean).join(',');
            } else if (k === 'fans_info') {
                const trend = json.data?.fansNumTrend || [];
                if (trend.length) darenInfo.fans_info.fansNum = trend[trend.length - 1].fansTotalAmount || 0;
                endpointsLoaded.add('fans_info');
                printDarenInfo('kwai_fans_hook');
            } else if (k === 'goods_portrait_info') {
                const cat = json.data?.hotSaleChannelInfo || [];
                const brand = json.data?.hotSaleBrandInfo || [];
                darenInfo.goods_portrait_info.categoryRank = cat.map(c => ({
                    name: c.hotSaleChannelName,
                    ratio: (c.hotSaleChannelRate || 0) / 10000
                }));
                const totalWeight = brand.length ? brand.length * (brand.length + 1) / 2 : 0;
                darenInfo.goods_portrait_info.brandRank = brand.map(b => {
                    const r = b.hotSaleBrandNo;
                    return {
                        name: b.hotSaleBrandTitle,
                        ratio: totalWeight ? ((brand.length - r + 1) / totalWeight) : 0
                    };
                });
                endpointsLoaded.add('goods_portrait_info');
                printDarenInfo('kwai_goods_portrait_hook');
            } else if (k === 'sales_analyses_info') {
                const d = json.data || {};
                darenInfo.sales_analyses_info.averagePrice = (d.avgItemPrice || 0) / 100;
                let totalSaleStr = d.totalSale || '';
                if (/万/.test(totalSaleStr)) {
                    const cleaned = totalSaleStr.replace(/万|元/g, '').trim();
                    if (cleaned.includes('-')) {
                        const [a, b] = cleaned.split('-');
                        darenInfo.overview_info.totalSale = `${Math.round(parseFloat(a || 0) * 10000)}-${Math.round(parseFloat(b || a || 0) * 10000)}`;
                    } else {
                        darenInfo.overview_info.totalSale = String(Math.round(parseFloat(cleaned || 0) * 10000));
                    }
                } else {
                    darenInfo.overview_info.totalSale = totalSaleStr;
                }
                if (darenInfo.overview_info.totalSale.includes('-')) {
                    const [l, h] = darenInfo.overview_info.totalSale.split('-');
                    darenInfo.overview_info.totalSalesLow = parseInt(l) || 0;
                    darenInfo.overview_info.totalSalesHigh = parseInt(h) || 0;
                }
                endpointsLoaded.add('sales_analyses_info');
                endpointsLoaded.add('overview_info');
                printDarenInfo('kwai_sales_hook');
            }
        }
    };

    // ===== 修改：runCollectSequence 对非抖音直接等待 =====
    const _oldRunCollectSequence = runCollectSequence;
    runCollectSequence = function (action, doneCb) {
        if (platform === 'douyin') return _oldRunCollectSequence(action, doneCb);
        // 非抖音：直接 3 秒或全部齐
        const start = performance.now();
        const INTERVAL = 150;
        const MAX_WAIT = 3000;
        (function loop() {
            const all = REQUIRED_KEYS.every(k => endpointsLoaded.has(k));
            if (all || performance.now() - start >= MAX_WAIT) {
                printFinalUpload(action);
                doneCb && doneCb();
            } else {
                setTimeout(loop, INTERVAL);
            }
        })();
    };

    // ===== runCollectSequence 按平台分支 =====
    const originalRunCollectSequence = runCollectSequence; // 现有（抖音专用）
    runCollectSequence = function (action, doneCb) {
        if (!started) { doneCb && doneCb(); return; }
        if (platform === 'douyin') {
            originalRunCollectSequence(action, doneCb);
            return;
        }
        if (platform === 'kwai') {
            // 主动刷新一次数据后等待
            fetchKwaiData(true).then(() => {
                waitPlatformData(action, doneCb, 3000);
            });
            return;
        }
        if (platform === 'wechat') {
            // 已靠拦截填充，直接等待
            waitPlatformData(action, doneCb, 3000);
            return;
        }
        // 其它未知平台直接打印
        waitPlatformData(action, doneCb, 1000);
    };

    function waitPlatformData(action, doneCb, maxWait) {
        const start = performance.now();
        const interval = 150;
        const reqKeys = REQUIRED_KEYS.slice();
        (function loop() {
            const all = reqKeys.every(k => endpointsLoaded.has(k));
            if (all) {
                console.log('[采集监控] 平台数据全部到齐 -> 打印');
                printFinalUpload(action);
                doneCb && doneCb();
            } else if (performance.now() - start >= maxWait) {
                const miss = reqKeys.filter(k => !endpointsLoaded.has(k));
                console.log('[采集监控] 平台等待超时 miss=', miss);
                printFinalUpload(action);
                doneCb && doneCb();
            } else {
                setTimeout(loop, interval);
            }
        })();
    }

    // ===== createBottomBarIfNeeded 保持 (不同平台依旧只需 darenId) =====
    const ACTIONS = {
        'update-daren-info': 'update',
        'add-to-public-sea': 'sea',
        'claim-daren': 'claim'
    };

    // createBottomBarIfNeeded 内替换为下面版本（增加隐藏标记逻辑）
    function createBottomBarIfNeeded() {
        if (window.__erpBarUserHidden) return; // 用户手动隐藏后不再自动创建
        if (document.getElementById('my-bottom-action-bar')) return;

        const bar = document.createElement('div');
        bar.id = 'my-bottom-action-bar';
        Object.assign(bar.style, {
            position: 'fixed', bottom: '0', left: '0', width: '100%', padding: '10px 18px',
            background: '#fff', borderTop: '1px solid #e8e8e8',
            boxShadow: '0 -2px 8px rgba(0,0,0,0.08)', display: 'flex',
            alignItems: 'center', boxSizing: 'border-box', fontSize: '14px',
            justifyContent: 'flex-start', zIndex: '9999'
        });

        const leftBox = document.createElement('div');
        Object.assign(leftBox.style, {
            display: 'flex', alignItems: 'center', gap: '12px', flexShrink: '0'
        });

        const userSpan = document.createElement('span');
        userSpan.id = 'erp-user-display';
        userSpan.textContent = '用户：' + (getErpAccount() || '未登录');
        Object.assign(userSpan.style, { color: '#333' });

        const logoutBtn = document.createElement('button');
        logoutBtn.textContent = '退出登录';
        Object.assign(logoutBtn.style, {
            height: '30px', padding: '0 14px', cursor: 'pointer',
            border: '1px solid #dcdfe6', background: '#fff', borderRadius: '4px',
            fontSize: '13px'
        });
        logoutBtn.onclick = () => {

            GM_xmlhttpRequest({
                method: 'POST',
                url: ERP_LOGOUT_URL,
                headers: {
                    Authorization: `Bearer ${getErpAccessToken()}`
                },
                timeout: 10000,
                onload: r => {
                    if (r.status === 200) handleAuthExpired();
                },
                onerror: () => showToast('注销请求失败', 'error'),
                ontimeout: () => showToast('注销请求超时', 'error')
            });

            
            __claimStatusCached = null;
            const claimBtn = document.getElementById('claim-daren');
            if (claimBtn) claimBtn.style.display = '';
            console.log('[采集监控][ERP] 已退出登录');
        };
        leftBox.appendChild(userSpan);
        leftBox.appendChild(logoutBtn);

        const actionsBox = document.createElement('div');
        actionsBox.id = 'erp-actions-box';
        Object.assign(actionsBox.style, {
            position: 'absolute', left: '50%', transform: 'translateX(-50%)',
            display: 'flex', alignItems: 'center', flexShrink: '0'
        });

        const btnDefs = [
            { text: '更新达人信息', id: 'update-daren-info', primary: false },
            { text: '加入团队公海', id: 'add-to-public-sea', primary: false },
            { text: '认领达人', id: 'claim-daren', primary: true }
        ];
        btnDefs.forEach(def => {
            const b = document.createElement('button');
            b.id = def.id;
            b.textContent = def.text;
            Object.assign(b.style, {
                border: '1px solid #dcdfe6', padding: '0 20px', height: '34px', lineHeight: '34px',
                borderRadius: '4px', cursor: 'not-allowed', fontSize: '14px', marginLeft: '16px',
                background: def.primary ? '#f54545' : '#fff',
                color: def.primary ? '#fff' : '#333',
                borderColor: def.primary ? '#f54545' : '#dcdfe6', opacity: '0.7'
            });
            b.disabled = true;
            b.addEventListener('click', e => {
                const btn = e.currentTarget;
                ensureErpToken(() => handleActionClick(btn));
            });
            actionsBox.appendChild(b);
        });

        // 旧 toggle 按钮改为“隐藏”触发完全收起
        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'erp-bar-toggle';
        toggleBtn.textContent = '隐藏';
        Object.assign(toggleBtn.style, {
            height: '30px', padding: '0 14px', cursor: 'pointer',
            border: '1px solid #dcdfe6', background: '#fff', borderRadius: '4px',
            fontSize: '13px', marginLeft: '16px', flexShrink: '0'
        });

        // 新：收起后仅保留一个独立“显示”按钮
        function createShowOnlyButton() {
            if (document.getElementById('erp-bar-show-btn')) return;
            const showBtn = document.createElement('button');
            showBtn.id = 'erp-bar-show-btn';
            showBtn.textContent = '显示操作栏';
            Object.assign(showBtn.style, {
                position: 'fixed', bottom: '10px', left: '14px',
                zIndex: '9999', padding: '6px 14px',
                background: '#f54545', color: '#fff',
                border: '1px solid #f54545', borderRadius: '4px',
                cursor: 'pointer', fontSize: '13px', boxShadow: '0 2px 8px rgba(0,0,0,0.18)'
            });
            showBtn.onclick = () => {
                // 还原栏
                if (window.__erpStoredBar) {
                   
                    document.body.appendChild(window.__erpStoredBar);
                    window.__erpStoredBar = null;
                }
                if (showBtn.parentNode) showBtn.parentNode.removeChild(showBtn);
                setCollapsed(false);
            };
            document.body.appendChild(showBtn);
        }

        function removeShowOnlyButton() {
            const s = document.getElementById('erp-bar-show-btn');
            if (s) s.remove();
        }

        function setCollapsed(collapsed) {
            if (collapsed) {
                window.__erpBarUserHidden = true;  // 标记为用户隐藏
                if (bar.parentNode) {
                    window.__erpStoredBar = bar;
                    bar.parentNode.removeChild(bar);
                }
                createShowOnlyButton();
            } else {
                window.__erpBarUserHidden = false; // 取消隐藏标记
                removeShowOnlyButton();
                if (!document.getElementById('my-bottom-action-bar')) {
                    document.body.appendChild(bar);
                }
            }
        }

        toggleBtn.onclick = () => setCollapsed(true);

        bar.appendChild(leftBox);
        bar.appendChild(actionsBox);
        bar.appendChild(toggleBtn);
        document.body.appendChild(bar);

        console.log('[采集监控] 已创建底部操作栏');

        setTimeout(() => {
            actionsBox.querySelectorAll('button').forEach(b => {
                b.disabled = false;
                b.style.opacity = '1';
                b.style.cursor = 'pointer';
            });
            ensureErpToken(() => checkClaimStatus());
        }, 1500);

        updateErpAccountDisplay();

        function reassessLayout() {
            if (!document.body.contains(bar)) return;
            actionsBox.style.position = 'absolute';
            actionsBox.style.left = '50%';
            actionsBox.style.transform = 'translateX(-50%)';
            const leftRect = leftBox.getBoundingClientRect();
            const actRect = actionsBox.getBoundingClientRect();
            if (leftRect.right + 12 > actRect.left) {
                actionsBox.style.position = 'static';
                actionsBox.style.transform = 'none';
                bar.style.justifyContent = 'space-between';
            } else {
                bar.style.justifyContent = 'space-between';
            }
        }
        window.addEventListener('resize', reassessLayout);
        const accObserver = new MutationObserver(reassessLayout);
        accObserver.observe(userSpan, { childList: true, characterData: true, subtree: true });
        window.__reassessActionBarLayout = reassessLayout;
        reassessLayout();
    }

    function removeBottomBarIfNeeded() {
        const bar = document.getElementById('my-bottom-action-bar');
        if (bar && !isValidPage()) {
            bar.remove();
            console.log('[采集监控] 已移除底部操作栏(离开目标页)');
        }
    }

    // ============== 新增：点击执行流程 ==============
    function handleActionClick(btn) {
        if (btn.disabled) return;
        const action = ACTIONS[btn.id];
        if (!action) return;

        // 防抖 & UI 状态
        btn.disabled = true;
        btn.style.opacity = '0.7';
        btn.style.cursor = 'not-allowed';
        const originalText = btn.textContent;
        btn.textContent = originalText + ' (处理中...)';

        // 记录点击时间戳（保持与 Python  侧逻辑习惯一致）
        btn.setAttribute('data-clicked', Date.now());

        // 执行采集操作
        runCollectSequence(action, () => {
            btn.textContent = originalText;
            setTimeout(() => {
                btn.disabled = false;
                btn.style.opacity = '1';
                btn.style.cursor = 'pointer';
            }, 3000);
        });

    }

    // 替换 runCollectSequence 为分步 3 秒(或提前)推进
    function runCollectSequence(action, doneCb) {
        if (!started) { doneCb && doneCb(); return; }

        const STEP_TIMEOUT = 3000;
        const STEP_INTERVAL = 150;

        // 统一分步等待封装
        function waitStep(expectKeys, clickFn, label, next) {
            try { clickFn && clickFn(); } catch (_) { }
            const start = performance.now();

            (function loop() {
                const allOk = expectKeys.every(k => endpointsLoaded.has(k));
                if (allOk) {
                    console.log(`[采集监控] ${label} 已到齐 -> 进入下一步, 用时 ${(performance.now() - start).toFixed(0)}ms`);
                    next();
                } else if (performance.now() - start >= STEP_TIMEOUT) {
                    const miss = expectKeys.filter(k => !endpointsLoaded.has(k));
                    console.log(`[采集监控] ${label} 等待超时(3s) 缺失: ${miss.join(',') || '无'} -> 强制进入下一步`);
                    next();
                } else {
                    setTimeout(loop, STEP_INTERVAL);
                }
            })();
        }

        // Step1: 粉丝分析
        function step1() {
            waitStep(
                ['fans_info'],
                () => {
                    revealEyes();                // 先尝试点击“眼睛”展开数据
                    clickTab('#daren-profile-main-tabs-tab-2'); // 粉丝分析
                },
                'Step1(fans_info)',
                step2
            );
        }

        // Step2: 带货分析（期望两个接口）
        function step2() {
            waitStep(
                ['goods_portrait_info', 'sales_analyses_info'],
                () => clickTab('#daren-profile-main-tabs-tab-4'),
                'Step2(goods_portrait + sales_analyses)',
                finalize
            );
        }

        function finalize() {
            // 最终再等待剩余必需接口（如果之前两个阶段已齐，会立即打印）
            waitForDataAndPrint(action, () => {
                doneCb && doneCb();
            });
        }

        // 启动
        step1();
    }

    function hasAllRequired() {
        return REQUIRED_KEYS.every(k => endpointsLoaded.has(k));
    }

    function waitForDataAndPrint(action, doneCb) {
        const start = performance.now();
        const MAX_WAIT = 3000; // 3秒
        const INTERVAL = 150;
        let printed = false;

        function tryPrint(trigger) {
            if (printed) return;
            printed = true;
            console.log(`[采集监控] 结束等待 trigger=${trigger} elapsed=${(performance.now() - start).toFixed(0)}ms loaded=`, Array.from(endpointsLoaded));
            printFinalUpload(action);
            doneCb && doneCb();
        }

        (function loop() {
            if (hasAllRequired()) {
                tryPrint('all-ready');
            } else if (performance.now() - start >= MAX_WAIT) {
                tryPrint('timeout');
            } else {
                setTimeout(loop, INTERVAL);
            }
        })();
    }

    function clickTab(selector) {
        const el = document.querySelector(selector);
        if (el) el.click();
    }

    // ============== 抖音达人查看联系方式 ==============
    function revealEyes() {
        document.querySelectorAll('img[src*="p3-infra.elabpic.com/tos-cn-i-ax5x5hote5"]').forEach(img => {
            try { img.click(); } catch (_) { }
        });
    }
    // ============== 抖音达人查看联系方式 ==============

    // 最终上传数据打印
    function printFinalUpload(action) {
        const flat = flattenSubDicts(darenInfo);
        console.group('[采集监控][最终上传数据]', action);
        console.log('flatten_sub_dicts =>', flat);
        console.log('full darenInfo =>', JSON.parse(JSON.stringify(darenInfo)));
        if (typeof GM_getValue === 'function') {
            console.log('accessToken.len=', (GM_getValue(ERP_ACCESS_TOKEN_KEY, '') || '').length);
        }
        console.groupEnd();

        // 新增：展示对话框
        try {
            showUploadDialog(darenInfo, action);
        } catch (err) {
            console.log('[采集监控] 展示上传对话框失败', err);
        }
    }

    // ====== 新增：中文标签映射 & 弹窗展示函数 ======
    const LABEL_MAP = {
        // profile
        darenId: '达人ID', dyUid: '抖音UID', headUrl: '头像', agency: 'MCN机构', darenNick: '达人昵称',
        darenLevel: '达人等级', mainCategory: '主类目', creditScore: '信用分', introduction: '简介',
        wineRatioNum: '酒类占比', liveShowCount: '直播次数', videoPubCount: '视频发布数', score: '综合分',
        // contact
        darenPhone: '手机号', darenWechat: '微信号', showPhone: '显示手机号', showWechat: '显示微信',
        // overview
        totalSale: '总销售额(区间)', darenType: '达人类型', totalSalesLow: '销售额下限', totalSalesHigh: '销售额上限',
        // fans
        fansNum: '粉丝数', fansFeature: '粉丝特征',
        // goods
        brandRank: '品牌排行', categoryRank: '类目排行',
        // sales
        averagePrice: '平均客单价', livePercentage: '直播GMV占比', liveSaleLow: '直播销售额下限', liveSaleHigh: '直播销售额上限',
        liveAveragePrice: '直播平均价', videoPercentage: '视频GMV占比', videoSaleLow: '视频销售额下限',
        videoSaleHigh: '视频销售额上限', videoAveragePrice: '视频平均价', liveVisitorCount: '直播访客数', videoWatchCount: '视频播放量'
    };

    function showUploadDialog(darenInfo, action) {
        // 先移除旧的
        const old = document.getElementById('erp-upload-overlay');
        if (old) old.remove();

        const overlay = document.createElement('div');
        overlay.id = 'erp-upload-overlay';
        Object.assign(overlay.style, {
            position: 'fixed', inset: '0', background: 'rgba(0,0,0,0.45)', zIndex: '999998',
            display: 'flex', alignItems: 'center', justifyContent: 'center'
        });

        const box = document.createElement('div');
        Object.assign(box.style, {
            width: '880px', height: '600px', background: '#fff', borderRadius: '8px',
            boxShadow: '0 4px 18px rgba(0,0,0,0.25)', display: 'flex', flexDirection: 'column',
            overflow: 'hidden', fontFamily: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Microsoft YaHei,Arial'
        });

        // 头部
        const header = document.createElement('div');
        header.textContent = '是否上传(信息偏差大请联系管理员核对)';
        Object.assign(header.style, {
            flex: '0 0 48px', lineHeight: '48px', padding: '0 20px', fontSize: '16px',
            fontWeight: '600', borderBottom: '1px solid #eee'
        });

        // 内容区域
        const content = document.createElement('div');
        Object.assign(content.style, {
            flex: '1 1 auto', overflow: 'auto', padding: '12px 16px', fontSize: '13px'
        });

        // 表格
        const table = document.createElement('table');
        Object.assign(table.style, {
            width: '100%', borderCollapse: 'collapse'
        });

        function addSection(title, rows) {
            if (!rows || !rows.length) return;
            const headTr = document.createElement('tr');
            const th = document.createElement('th');
            th.colSpan = 2;
            th.textContent = title;
            Object.assign(th.style, {
                textAlign: 'left', background: '#f5f7fa', padding: '6px 10px',
                fontWeight: '600', border: '1px solid #e5e5e5'
            });
            headTr.appendChild(th);
            table.appendChild(headTr);
            rows.forEach(([k, v]) => {
                if (k === 'dyUid' && (platform == 'kwai' || platform == 'wechat')) return; // 快手无此两项

                const tr = document.createElement('tr');
                const td1 = document.createElement('td');
                const td2 = document.createElement('td');
                td1.textContent = LABEL_MAP[k] || k;
                Object.assign(td1.style, {
                    width: '160px', padding: '6px 8px', border: '1px solid #eee',
                    background: '#fcfcfc', verticalAlign: 'top'
                });
                // 值格式处理
                let valStr = '';
                if (v == null) valStr = '';
                else if (typeof v === 'object') {
                    if (Array.isArray(v)) {
                        if (v.length && typeof v[0] === 'object') {
                            valStr = v.map(o => {
                                if (o && typeof o === 'object') {
                                    if ('name' in o && 'ratio' in o) {
                                        return `${o.name}:${(o.ratio * 100).toFixed(2)}%`;
                                    }
                                    return JSON.stringify(o);
                                }
                                return String(o);
                            }).join(' | ');
                        } else {
                            valStr = v.join(',');
                        }
                    } else {
                        valStr = JSON.stringify(v);
                    }
                } else valStr = String(v);

                // 特殊：头像
                if (k === 'headUrl' && valStr) {
                    const img = document.createElement('img');
                    img.src = valStr;
                    img.alt = '头像';
                    Object.assign(img.style, { width: '40px', height: '40px', borderRadius: '4px', objectFit: 'cover', border: '1px solid #ddd' });
                    td2.appendChild(img);
                    const span = document.createElement('div');
                    span.textContent = valStr;
                    Object.assign(span.style, { fontSize: '`11`px', color: '#888', marginTop: '4px', wordBreak: 'break-all' });
                    td2.appendChild(span);
                } else {
                    td2.textContent = valStr;
                }
                Object.assign(td2.style, {
                    padding: '6px 8px', border: '1px solid #eee', wordBreak: 'break-all', lineHeight: '1.4'
                });
                tr.appendChild(td1); tr.appendChild(td2);
                table.appendChild(tr);
            });
        }

        // 分块数据
        addSection('基础信息', Object.entries(darenInfo.profile_info || {}));
        addSection('联系方式', Object.entries(darenInfo.contact_info || {}));
        addSection('概览信息', Object.entries(darenInfo.overview_info || {}));
        addSection('粉丝数据', Object.entries(darenInfo.fans_info || {}));
        addSection('商品/品类', Object.entries(darenInfo.goods_portrait_info || {}));
        addSection('销售分析', Object.entries(darenInfo.sales_analyses_info || {}));

        content.appendChild(table);

        // 底部按钮
        const footer = document.createElement('div');
        Object.assign(footer.style, {
            flex: '0 0 56px', borderTop: '1px solid #eee', display: 'flex',
            alignItems: 'center', justifyContent: 'flex-end', padding: '0 20px', gap: '12px'
        });

        const btnCancel = document.createElement('button');
        btnCancel.textContent = '取消';
        styleBtn(btnCancel, false);
        btnCancel.onclick = () => {
            console.log('[采集监控] 用户取消上传');
            overlay.remove();
        };

        const btnOk = document.createElement('button');
        btnOk.textContent = '确认';
        styleBtn(btnOk, true);
        btnOk.onclick = async () => {
            if (btnOk.getAttribute('data-busy') === '1') return;
            btnOk.setAttribute('data-busy', '1');
            const ori = btnOk.textContent;
            btnOk.textContent = '上传中...';
            const res = await uploadToServer(darenInfo, action);
            btnOk.textContent = ori;
            btnOk.removeAttribute('data-busy');
            if (res.ok) {
                overlay.remove();
            } else {
                // 失败不关闭，让用户可再次确认
            }
        };

        footer.appendChild(btnCancel);
        footer.appendChild(btnOk);

        // 按钮样式
        function styleBtn(btn, primary) {
            Object.assign(btn.style, {
                minWidth: '88px', height: '34px', padding: '0 18px', cursor: 'pointer',
                borderRadius: '4px', fontSize: '14px', border: '1px solid',
                background: primary ? '#f54545' : '#fff',
                borderColor: primary ? '#f54545' : '#dcdfe6',
                color: primary ? '#fff' : '#333'
            });
            btn.onmouseenter = () => btn.style.filter = 'brightness(0.95)';
            btn.onmouseleave = () => btn.style.filter = 'none';
        }

        box.appendChild(header);
        box.appendChild(content);
        box.appendChild(footer);
        overlay.appendChild(box);
        document.body.appendChild(overlay);
    }

    // 修改标题轮询触发内：加入 createBottomBarIfNeeded
    // ...existing code...
    // 在原 titleTimer 与 初始标题匹配设置 started=true 的地方之后插入以下补充调用

    // 由于不能大面积重写，使用一个轮询保障底栏存在 (增加隐藏判断)
    const barEnsureTimer = setInterval(() => {
        if (window.__erpBarUserHidden) return; // 用户隐藏时禁止自动恢复
        if (isValidPage()) {
            createBottomBarIfNeeded();
        }
    }, 1000);

    // 额外轮询（更快感知路由变化）
    const quickRouteWatch = setInterval(() => {
        if (!isValidPage()) removeBottomBarIfNeeded();
    }, 400);

    const observerStartGuard = setInterval(() => {
        if (started && darenInfo.profile_info.darenId) {
            if (isValidPage()) createBottomBarIfNeeded();
            else removeBottomBarIfNeeded();
            clearInterval(observerStartGuard);
        }
    }, 200);

    // 新增：依据 URL + 已获取到的 darenId 判定页面是否有效
    function isValidPage() {
        const href = location.href;
        return (
            /:\/\/buyin\.jinritemai\.com\/dashboard\/servicehall\/daren-profile/.test(href) ||
            /:\/\/cps\.kwaixiaodian\.com\/zone\/daren-match\/daren-detail/.test(href) ||
            /:\/\/store\.weixin\.qq\.com\/shop\/findersquare\/finder-detail/.test(href)
        );
    }

    // 在底部（或相关工具函数附近）新增
    function updateErpAccountDisplay() {
        const el = document.getElementById('erp-user-display');
        if (el) {
            const acc = getErpAccount();
            el.textContent = '用户：' + (acc || '未登录');
        }
    }

    // 可在脚本加载后尝试一次
    updateErpAccountDisplay();

    /***************** TabId 监测（替换：使用 GM_getTab/GM_getTabs，移除 chrome 依赖） *****************/
    (function installTabIdWatcher(){
        if (window.__daren_tabid_installed__) return;
        window.__daren_tabid_installed__ = true;

        const TAB_KEY = '__daren_tab_uid';
        const LISTENERS = [];
        let lastEmitted = null;

        function genId(){
            return 'T-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2,8);
        }

        function emit(id){
            if(!id || id === lastEmitted) return;
            lastEmitted = id;
            window.__SHIPIN_TAB_ID = id; // 与另一个脚本共享字段
            LISTENERS.forEach(fn => { try{ fn(id); }catch{} });
            console.log('[daren][tabId] emit ->', id);
        }

        async function ensureTabId(){
            try{
                const tab = await GM_getTab();
                if(!tab[TAB_KEY]){
                    tab[TAB_KEY] = genId();
                    await GM_saveTab(tab);
                }
                emit(tab[TAB_KEY]);
            }catch{
                emit(window.__SHIPIN_TAB_ID || genId());
            }
        }

        async function refresh(){
            await ensureTabId();
            try{
                if(typeof GM_getTabs === 'function'){
                    const tabs = await GM_getTabs();
                    let found = false;
                    for(const k in tabs){
                        const t = tabs[k];
                        if(t && t[TAB_KEY] === window.__SHIPIN_TAB_ID){ found = true; break; }
                    }
                    if(!found){
                        const tab = await GM_getTab();
                        tab[TAB_KEY] = genId();
                        await GM_saveTab(tab);
                        emit(tab[TAB_KEY]);
                    }
                }
            }catch{}
        }

        window.__onShipinTabIdChange = fn => { if(typeof fn === 'function') LISTENERS.push(fn); };
        window.__getShipinTabId = () => window.__SHIPIN_TAB_ID;

        refresh();
        setInterval(refresh, 4000);
        window.addEventListener('visibilitychange', () => { if(!document.hidden) refresh(); });
        window.addEventListener('focus', refresh);
    })();

})();